'use strict';

const qs = require('querystring');

class bodyparser {

  constructor (options = {}) {
    this.maxFiles = 15;
    if (typeof options === 'object') {
      if (options.maxFiles 
        && typeof options.maxFiles === 'number' 
        && parseInt(options.maxFiles) > 0)
      {
        this.maxFiles = parseInt(options.maxFiles);
      }
    }
    this.pregUpload = /multipart.* boundary.*=/i;
    this.formType = 'application/x-www-form-urlencoded';
    this.methods = ['POST', 'PUT', 'PATCH', 'DELETE'];
  }
  /*
    解析上传文件数据的函数，此函数解析的是整体的文件，
    解析过程参照HTTP/1.1协议。
  */
  parseUploadData (ctx) {
    let bdy = ctx.headers['content-type'].split('=')[1];
    bdy = bdy.trim();
    bdy = `--${bdy}`;
    //var end_bdy = bdy + '--';
    let bdy_crlf = `${bdy}\r\n`;
    let crlf_bdy = `\r\n${bdy}`;
  
    let file_end = 0;
    let file_start = 0;
  
    file_start = ctx.rawBody.indexOf(bdy_crlf);
    if (file_start < 0) {
      return ;
    }
    file_start += bdy_crlf.length;

    let i=0; //保证不出现死循环或恶意数据产生大量无意义循环
    while(i < this.maxFiles) {
      file_end = ctx.rawBody.indexOf(crlf_bdy, file_start);
      if (file_end <= 0) { break; }
  
      this.parseSingleFile(ctx, file_start, file_end);
      file_start = file_end + bdy_crlf.length;
      i++;
    }
  }

  parseSingleFile (ctx, start_ind, end_ind) {
    let header_end_ind = ctx.rawBody.indexOf('\r\n\r\n',start_ind);
  
    let header_data = ctx.rawBody.toString('utf8', start_ind, header_end_ind);
    
    let file_post = {
      filename    : '',
      'content-type'  : '',
      start       : 0,
      end       : 0,
      length      : 0,
    };
    
    file_post.start = header_end_ind+4;
    file_post.end = end_ind;
    file_post.length = end_ind - 4 - header_end_ind;
  
    //parse header
    if (header_data.search("Content-Type") < 0) {
      //post form data, not file data
      let form_list = header_data.split(";");
      let tmp;
      let nind = 0;
      for(let i=0; i<form_list.length && i < 10; i++) {
        tmp = form_list[i].trim();
        nind = tmp.indexOf('name="');
        if (nind > -1) {
          let name = tmp.substring(nind+6, tmp.length-1).trim();
          ctx.body[name] = ctx.rawBody.toString('utf8', 
                    file_post.start, 
                    file_post.end);
          break;
        }
      }
    } else {
      //file data
      let form_list = header_data.split("\r\n").filter(s => s.length > 0);
      let tmp_name = form_list[0].split(";");
  
      let name = '';
      let fnameind = 0;
      for (let i=0; i<tmp_name.length && i < 10; i++) {
        fnameind = tmp_name[i].indexOf('filename="');
        if (fnameind > -1) {
          file_post.filename = tmp_name[i]
                      .substring(fnameind+10,
                        tmp_name[i].length-1
                      ).trim();
          continue;
        }
        fnameind = tmp_name[i].indexOf('name="');
        if (fnameind > -1) {
          name = tmp_name[i].substring(
                  fnameind+6, 
                  tmp_name[i].length-1
                ).trim();
        }
      }
  
      if (name == '') {
        return ;
      }
  
      if (form_list.length > 0) {
        file_post['content-type'] = form_list[1].split(":")[1].trim();
      }
      
      if (ctx.files[name] === undefined) {
        ctx.files[name] = [file_post];
      } else {
        ctx.files[name].push(file_post);
      }
    }
  }

  checkUploadHeader (headerstr) {
    if (this.pregUpload.test(headerstr)) {
      return true;
    }
    return false;
  }

  middleware () {
    var self = this;
    return async (ctx, next) => {
      if ((typeof ctx.rawBody == 'string' || ctx.rawBody instanceof Buffer) 
        && ctx.rawBody.length > 0 && self.methods.indexOf(ctx.method) >= 0) {

        if (self.checkUploadHeader(ctx.headers['content-type'])) {
          ctx.isUpload = true;
          self.parseUploadData(ctx, self.maxFiles);

        } else if (ctx.headers['content-type'] && ctx.headers['content-type'].indexOf(self.formType) >= 0) {
          ctx.body = qs.parse(ctx.rawBody.toString('utf8'));
        } else {
          ctx.body = ctx.rawBody.toString('utf8');
        }

      }
      await next();
    };
  }
}

module.exports = bodyparser;
